﻿using System;
using System.Diagnostics;
using System.Threading;

namespace Prometheus.UIUtility.Storage
{
    /// <summary>
    /// Property with twin backing fields
    /// One of the fields is kept in-memory
    /// Another field must be implemented
    /// </summary>
    /// <typeparam name="T"></typeparam>
    [DebuggerDisplay("Value={ValueForDebugger}")]
    public abstract class StorageProperty<T>
    {
        private readonly Func<T> _lazyLoad;
        private readonly string _name;
        private readonly bool _singletonLock;
        private bool _isInitialized;
        private T _localValue;

        protected StorageProperty(string name, bool singletonLock, Func<T> lazyLoad)
        {
            _name = name;
            _singletonLock = singletonLock;
            _lazyLoad = lazyLoad;
        }

        public bool Exists
        {
            get { return Do(() => IsPropertyExists(_name)); }
        }

        public object ValueForDebugger
        {
            get
            {
                if (!_isInitialized && !Exists) throw new InvalidOperationException("Value has not been created yet");
                return _localValue;
            }
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public T Value
        {
            get
            {
                return Do(() =>
                    {
                        if (!_isInitialized)
                        {
                            if (!Exists) SetDefault();
                            else
                            {
                                _localValue = GetValue(_name);
                            }
                        }

                        return _localValue;
                    });
            }
            set
            {
                Do<object>(() =>
                    {
                        SetValue(_name, value);
                        _localValue = value;
                        _isInitialized = true;
                        return null;
                    });
            }
        }

        /// <summary>
        /// Check if need lock
        /// </summary>
        private TRetT Do<TRetT>(Func<TRetT> func)
        {
            if (_singletonLock)
                Monitor.Enter(IsolatedStoragePropertyHelper.ThreadLocker);

            TRetT result = func();

            if (_singletonLock)
                Monitor.Exit(IsolatedStoragePropertyHelper.ThreadLocker);
            return result;
        }

        protected abstract bool IsPropertyExists(string name);

        protected abstract T GetValue(string name);
        protected abstract void SetValue(string name, T value);

        public void SetDefault()
        {
            Value = _lazyLoad == null ? default(T) : _lazyLoad();
        }
    }

    #region Nested type: IsolatedStoragePropertyHelper

    internal static class IsolatedStoragePropertyHelper
    {
        public static readonly object ThreadLocker = new object();
    }

    #endregion
}